Netlifyのデフォルトキャッシュ設定はどう振る舞う?
静的配信・サーバーレスバックエンドの Netlify ではデフォルトのブラウザ向けキャッシュ設定は次の通りです。
Cache-Control: max-age=0,must-revalidate,public
このヘッダーがどのようにキャッシュされるか説明できますか?
「キャッシュはするけど、信頼しないでね(“please cache this content, and then do not trust your cache”) *1」、という意味です。
ウェブパフォーマンスでキャッシュは大事
ウェブサービスでコンテンツのキャッシュ設定は重要です。
正しくキャッシュすると、サーバーの負荷軽減やクライアントの表示速度の改善など、様々なメリットがあります。 一方で、設定を誤ると、古いキャッシュがいつまでも表示されたり、個人的なコンテンツのキャッシュが他人にも利用されてしまうといった不具合にも繋がります。
CDNやLast-Modified
ディレクティブなど、キャッシュ技術は前世紀から存在し、進化し続けてきました。
本記事では、長い歴史のある複雑なキャッシュ仕様のなかから、今風なウェブサービス Netlify が利用するキャッシュ設定
Cache-Control: max-age=0,must-revalidate,public
に限定して、超ピンポイントに解説します。
Netlify のキャッシュ設定をはどうなっている?
Netlify のサイトトップに HTTP GET リクエストして、レスポンスを確認します。
$ curl -I https://www.netlify.com/ HTTP/2 200 cache-control: public, max-age=0, must-revalidate content-type: text/html; charset=UTF-8 date: Mon, 18 Jan 2021 18:24:13 GMT etag: "3efb3ad0a8ab2881aff806f3d3b13d31-ssl" link: <https://www.netlify.com/>; rel="canonical" referrer-policy: no-referrer-when-downgrade strict-transport-security: max-age=31536000; includeSubDomains; preload x-content-type-options: nosniff x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block age: 11706 content-length: 409714 server: Netlify set-cookie: nf_ab=0.866427; expires=Tue, 18-Jan-2022 21:39:19 GMT; path=/ x-nf-request-id: 51cdec21-0e42-4a13-affd-4b120bde3037-6087705
レスポンスにある
- cache-control: public, max-age=0, must-revalidate
- etag: "3efb3ad0a8ab2881aff806f3d3b13d31-ssl"
がブラウザ(クライアント)のキャッシュに関連するヘッダーです。
キャッシュしても良いけど(public
)、即時に古くなるので(max-age=0
)、利用する前にコンテンツが変わっていないこと(etag
)を検証してね(must-revalidate
)、ということを意味します。
ディレクティブごとに意味を確認します。
Cache-Control: public
MDN によると、"The response may be stored by any cache, even if the response is normally non-cacheable" とあります。
サーバーレスポンスをキャッシュするレイヤーは
- クライアント(ブラウザ)
- クライアント-サーバー間にあるCDN/プロキシサーバーといったキャッシュ層(shared cache)
の2レイヤーがあります。
"any cache" というのは、このどちらのレイヤーでもキャッシュして良いことを意味します。
private の場合、クライアントだけがキャッシュ可能です。
Cache-Control: max-age
ブラウザは、コンテンツをキャッシュ後、max-age
で指定された秒数だけローカルキャッシュを利用できます。
よく似たディレクティブに s-maxage
(s-max-age
ではないことに注意)があります。
このディレクティブは、CDN/プロキシといった shared cache の保持期間を定義します。
Netlify の場合は max-age=0
のため、キャッシュは即時に古く(stale)なります。キャッシュが max-age
を超えて stale になったらどうすればよいのか?
そこを定義しているのが、次に紹介する must-revalidate
です。
Cache-Control: must-revalidate
must-revalidate
の場合、サーバーに問い合わせてコンテンツが変わっていないことが確認できた場合のみ(re-validate)、キャッシュを利用できます。
コンテンツが変わっていないことを確認するために利用するのが、 レスポンスヘッダーに含まれる etag です。 etag はコンテンツのハッシュ値です。
サーバーに対して etag の値を利用して条件付きリクエストを行います。
リクエストヘッダーの if-non-match
に etag の値を渡して、条件付きリクエストをします。
# 初回リクエスト $ curl -I https://www.netlify.com/ HTTP/2 200 cache-control: public, max-age=0, must-revalidate content-type: text/html; charset=UTF-8 date: Mon, 18 Jan 2021 19:29:28 GMT etag: "3efb3ad0a8ab2881aff806f3d3b13d31-ssl" ... # 2回目のリクエスト $ curl -I https://www.netlify.com/ \ -H 'if-none-match:"3efb3ad0a8ab2881aff806f3d3b13d31-ssl"' HTTP/2 304 date: Tue, 19 Jan 2021 13:57:25 GMT etag: "3efb3ad0a8ab2881aff806f3d3b13d31-ssl" cache-control: public, max-age=0, must-revalidate server: Netlify x-nf-request-id: 6b1423cb-32bf-4f87-846d-18c13d37e6f9-33048218
HTTP レスポンス 304 : Not Modified
から、ブラウザキャッシュを利用して大丈夫です。
Cache-Control:no-cache はキャッシュしないわけではない
Cache-Control: max-age=0, must-revalidate
は Cache-Control: no-cache
と書いても同じです。
これまでの説明のように、no-cache
の字面とは裏腹に、キャッシュするのでご注意ください。
キャッシュしないのは Cache-Control: no-store
です。
must-revalidate が存在しない場合
must-revalidate
が存在せず、 Cache-Control: max-age=0
だけの場合はどうなるでしょうか?
MDN によると、サーバーが落ちているなどして validate できなかった場合、古いキャッシュが利用されることがあります *2。 このあたりの挙動は、ブラウザ依存と思われます。
バリデーションに失敗した時は古くなったキャッシュを返す *3 stale-if-error
というディレクティブも用意されていますが、Chrome/Firefox ですら正式対応していません。
毎回サーバーに validate するのは無駄なのでは?
index.html ファイルなどは、上述の must-revalidate 方式で良いかもしれませんが、JavaScriptなどのアセット系ファイルは更新頻度がレアのため、都度 revalidate が発生するのはもったいないです。
このようなあまり変わらない(immutableな)アセットの場合、webpack のようなビルドツールでファイル名にハッシュ値を含めてリリース間でURLが重複しないようにし、キャッシュ期間(max-age)を長くし、バリデーション無しにブラウザキャッシュを積極的に活用するようにします。
実際、Netlify公式サイト内のコンテンツを確認すると
- https://cdn.netlify.com/js/2a9d26f41138b72b65d1b93887839f96e34c5511/blog.js
- https://cdn.netlify.com/bundles/771044017fb819f862d8fe5b1af755ef5239ab22.js
というような URL が存在します。
これらに対してリクエストすると、cache-control: public, max-age=31556926
というように、約1年キャッシュする設定がかえってきました。
$ curl -I https://cdn.netlify.com/js/2a9d26f41138b72b65d1b93887839f96e34c5511/blog.js HTTP/2 200 accept-ranges: bytes access-control-allow-origin: * cache-control: public, max-age=31556926 ...
キャッシュ設定は難しい
Netlifyのキャッシュ設定を例に、を中心としたブラウザキャッシュ設定について解説しました。
ブラウザキャッシュに限っても、今回紹介した Cache-Control
以外にも、Expires
や Last-Modified
など複数の方法があります。
ブラウザキャッシュ以外にも、CDNやサーバーサイドでもキャッシュ可能です。
要件に合わせて、正しい層で然るべき設定をしましょう。
参照
Cache-Control
周りをより深く学びたい人のためのリンク集です。
- 渋川 よしき 『Real World HTTP 第2版』 2.8節 キャッシュ
- Cache-Control - HTTP | MDN
- 304 Not Modified - HTTP | MDN
- Love your cache ❤️
- Prevent unnecessary network requests with the HTTP Cache
- Better Living Through Caching | Netlify
- Cache-Control in the wild | Fastly